package com.easy.module.generator.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.easy.framework.common.exception.ServiceException;
import com.easy.framework.common.utils.AssertUtil;
import com.easy.framework.common.utils.Func;
import com.easy.framework.common.utils.ServletUtils;
import com.easy.framework.common.utils.StringUtil;
import com.easy.framework.common.utils.date.DateUtils;
import com.easy.module.generator.config.GeneratorProperties;
import com.easy.module.generator.entity.GenTable;
import com.easy.module.generator.entity.GenTableField;
import com.easy.module.generator.enums.ComponentType;
import com.easy.module.generator.mapper.GenTableMapper;
import com.easy.module.generator.model.CodeModel;
import com.easy.module.generator.query.GenTableQuery;
import com.easy.module.generator.service.IGenTableFieldService;
import com.easy.module.generator.service.IGenTableService;
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.ByteArrayOutputStream;
import java.io.StringWriter;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 生成表Service业务层处理
 *
 * @author Yang Huijing (Email:1073602@qq.com)
 * @date 2024-11-08
 */
@RequiredArgsConstructor
@Service
public class GenTableServiceImpl extends ServiceImpl<GenTableMapper, GenTable> implements IGenTableService {

    final GeneratorProperties generatorProperties;

    final IGenTableFieldService genTableFieldService;

    final Configuration freeMarkerConfig;

    @Override
    public LambdaQueryWrapper<GenTable> lambdaQuery(GenTableQuery genTableQuery) {
        return lambdaQueryOptional(genTableQuery).orElse(lambdaQueryWrapper()).orderByDesc(GenTable::getCreatedAt);
    }

    @Override
    public List<GenTable> listDbTable(GenTableQuery query) {
        return baseMapper.listDbTable(query.getTableName(), query.getTableComment(), generatorProperties.getExcludeTables());
    }

    private String getSimplifyName(String name) {
        if (Func.isNotEmpty(generatorProperties.getRemovePrefix())) {
            for (String prefix : generatorProperties.getRemovePrefix()) {
                String s = name.replaceFirst(prefix, "");
                if (!s.equals(name)) {
                    return s;
                }
            }
        }
        return name;
    }

    private void setField(GenTable table, GenTableField field) {
        field.setTableId(table.getId());
        field.setJavaFieldName(StringUtil.toCamelCaseLowerFirst(field.getFieldName()));
        String dataType = field.getFieldDataType();
        if (dataType.indexOf("(") > 0 && dataType.indexOf(",") == -1) {
            field.setDataLength(Integer.parseInt(dataType.substring(dataType.indexOf("(") + 1, dataType.indexOf(")"))));
        } else if (dataType.indexOf("(") > 0 && dataType.indexOf(",") > 0) {
            field.setDataLength(Integer.parseInt(dataType.substring(dataType.indexOf("(") + 1, dataType.indexOf(","))));
            field.setDataPrecision(Integer.parseInt(dataType.substring(dataType.indexOf(",") + 1, dataType.indexOf(")"))));
        }
        if(field.getIsPrimaryKey().equals("1")){
            field.setIsRequired("1");
        }
        field.setJavaDataType(StringUtil.toJavaType(dataType));
        field.setComponent(ComponentType.input.name());
        long count = generatorProperties.getExcludeFormFields().stream().filter(f -> f.equals(field.getFieldName())).count();
        field.setIsInsert(count > 0 ? "0" : "1");
        field.setIsEdit(field.getIsInsert());
        long count2 = generatorProperties.getExcludeListFields().stream().filter(f -> f.equals(field.getFieldName())).count();
        field.setIsList(count2 > 0 ? "0" : "1");
    }

    private void setTable(GenTable table) {
        table.setAuthor(generatorProperties.getAuthor());
        table.setPackageName(generatorProperties.getPackageName());
        String name = getSimplifyName(table.getTableName());
        table.setPath(name.replace("_", "/"));
        table.setClassName(StringUtil.toCamelCase(name));
        table.setContextPath(ServletUtils.getRequest().getContextPath());
        table.setBaseClass("BaseEntity");
        table.setSign(table.getTableName().replace("_", ":"));
    }

    /**
     * 导入表
     *
     * @param tables 导入表列表
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void importDbTable(List<GenTable> tables) {
        try {
            Iterator<GenTable> iterator = tables.iterator();
            while (iterator.hasNext()) {
                GenTable table = iterator.next();
                if (exists(GenTable::getTableName, table.getTableName())) {
                    iterator.remove();
                } else {
                    table.setMenuId(0L);
                    table.setFields(baseMapper.listDbFieldByTableName(table.getTableName()));
                    setTable(table);
                    table.setUpdatedAt(new Date());
                    save(table);
                    table.getFields().forEach(field -> setField(table, field));
                    genTableFieldService.saveBatch(table.getFields());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException("导入失败：" + e.getMessage());
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void synchDbTable(Long tableId) {
        GenTable table = getById(tableId);
        AssertUtil.isNotEmpty(table, "表不存在");
        GenTable dbTable = baseMapper.dbTableByTableName(table.getTableName(), generatorProperties.getExcludeTables());
        AssertUtil.isNotEmpty(dbTable, "数据库表[" + table.getTableName() + "]不可用或不存在");
        table.setFields(genTableFieldService.listByTableId(tableId));

        setTable(dbTable);
        dbTable.getFields().forEach(field -> setField(table, field));

        table.setTableComment(dbTable.getTableComment());

        List<Long> removeFieldIdList = new ArrayList<>();
        table.getFields().forEach(field -> {
            dbTable.getFields().stream().filter(f -> f.getFieldName().equals(field.getFieldName())).findFirst().ifPresentOrElse(v -> {
            }, () -> removeFieldIdList.add(field.getId()));
        });
        List<GenTableField> addFieldIdList = new ArrayList<>();
        List<GenTableField> updateFieldIdList = new ArrayList<>();
        dbTable.getFields().forEach(f -> {
            GenTableField tf =  table.getFields().stream().filter(field -> field.getFieldName().equals(f.getFieldName())).findFirst().orElse(null);
            if(Func.isNotEmpty(tf)){
                tf.setFieldComment(f.getFieldComment());
                tf.setIsRequired(f.getIsRequired());
                tf.setIsPrimaryKey(f.getIsPrimaryKey());
                tf.setIsAutoIncrement(f.getIsAutoIncrement());
                tf.setFieldDataType(f.getFieldDataType());
                tf.setDefaultValue(f.getDefaultValue());
                tf.setJavaFieldName(f.getFieldName());
                tf.setDataLength(f.getDataLength());
                tf.setDataPrecision(f.getDataPrecision());
                tf.setJavaDataType(f.getJavaDataType());
                updateFieldIdList.add(tf);
            }else{
                addFieldIdList.add(f);
            }
        });
        updateById(table);
        if(Func.isNotEmpty(removeFieldIdList)){
            removeBatchByIds(removeFieldIdList);
        }
        if(Func.isNotEmpty(updateFieldIdList)){
            genTableFieldService.updateBatchById(updateFieldIdList);
        }
        if(Func.isNotEmpty(addFieldIdList)){
            genTableFieldService.saveBatch(addFieldIdList);
        }
    }

    @Override
    public List<GenTable> listByTableId(List<Long> tableIds) {
        List<GenTable> tableList = list(lambdaQueryWrapper().in(GenTable::getId, tableIds));
        if (Func.isNotEmpty(tableList)) {
            List<GenTableField> fields = genTableFieldService.listByTableIds(tableIds);
            Map<Long, List<GenTableField>> fieldMap = fields.stream().collect(Collectors.groupingBy(GenTableField::getTableId));
            for (GenTable table : tableList) {
                List<GenTableField> tableFields = fieldMap.get(table.getId());
                table.setFields(tableFields);
            }
        }
        return tableList;
    }

    private List<CodeModel> getCodeModels(GenTable table) {
        String moduleName = Func.isNotEmpty(table.getModuleName()) ? table.getModuleName() + "/" : "";
        return List.of(
                new CodeModel("Entity.java", String.format("entity/%s.java", table.getClassName()))
                , new CodeModel("Query.java", String.format("query/%sQuery.java", table.getClassName()))
                , new CodeModel("Mapper.java", String.format("mapper/%sMapper.java", table.getClassName()))
                , new CodeModel("Service.java", String.format("service/I%sService.java", table.getClassName()))
                , new CodeModel("ServiceImpl.java", String.format("service/impl/%sServiceImpl.java", table.getClassName()))
                , new CodeModel("Controller.java", String.format("controller/%sController.java", table.getClassName()))
                , new CodeModel("Mapper.xml", String.format("mapper/xml/%sMapper.xml", table.getClassName()))
                , new CodeModel("Menu.sql", String.format("sql/%s.menu.sql", table.getTableName()))
                , new CodeModel("Api.ts", String.format("api/%s%s.ts", moduleName, table.getClassNameLowerFirst()))
                , new CodeModel("Index.vue", String.format("views/%s%s/index.vue", moduleName, table.getClassNameLowerFirst()))
        );
    }

    @SneakyThrows
    @Override
    public List<CodeModel> generatorCode(List<Long> tableIds) {
        List<GenTable> tableList = listByTableId(tableIds);
        AssertUtil.isNotEmpty(tableList, "表不存在");
        List<CodeModel> list = new ArrayList<>();
        for (GenTable table : tableList) {
            if (Func.isNotEmpty(table.getModuleName())) {
                table.setPackageName(table.getPackageName() + "." + table.getModuleName());
                table.setSign(table.getModuleName() + ":" + table.getSign());
            }
            table.setClassNameLowerFirst(StringUtil.toCamelCaseLowerFirst(table.getClassName()));
            table.setDictCodes(table.getFields().stream().filter(w -> Func.isNotEmpty(w.getDictCode())).map(f -> f.getDictCode()).collect(Collectors.toList()));
            Map<String, Object> variables = new HashMap<>();
            variables.put("table", table);
            variables.put("excludeFields", generatorProperties.getExcludeFields());
            variables.put("date", DateUtils.formatDate(new Date()));
            List<CodeModel> codeList = getCodeModels(table);
            for (CodeModel model : codeList) {
                Template template = freeMarkerConfig.getTemplate(model.getFtl().concat(".ftl"));
                try (StringWriter writer = new StringWriter()) {
                    template.process(variables, writer);
                    model.setCode(writer.toString());
                }
            }
            list.addAll(codeList);
        }
        return list;
    }

    @SneakyThrows
    @Override
    public byte[] download(List<Long> tableIds) {
        List<CodeModel> codeModels = generatorCode(tableIds);
        if (Func.isNotEmpty(codeModels)) {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ZipOutputStream zipOut = new ZipOutputStream(byteArrayOutputStream);
            for (CodeModel model : codeModels) {
                ZipEntry zipEntry = new ZipEntry(model.getFilename());
                zipOut.putNextEntry(zipEntry);
                zipOut.write(model.getCode().getBytes());
                zipOut.closeEntry();
            }
            zipOut.close();
            return byteArrayOutputStream.toByteArray();
        }
        return null;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean deleteByIds(List<Long> tableIds) {
        removeBatchByIds(tableIds);
        genTableFieldService.remove(Wrappers.<GenTableField>lambdaQuery().in(GenTableField::getTableId, tableIds));
        return true;
    }
}
